using System.Reflection;
using Crux.Core.Reflections;

namespace Crux.Core.Domains;

/// <summary>
/// DomainEventHandlerFinder
/// </summary>
public class DomainEventHandlerFinder
    : IDomainEventHandlerFinder
{
    #region static properties

    /// <summary>
    ///
    /// </summary>
    private static RuntimeTypeHandle[]? RuntimeTypeHandles;

    #endregion

    #region private fields

    private int _lockState = 0;
    private static readonly IAssemblyFinder _assemblyFinder = new AssemblyFinder();

    /// <summary>
    ///
    /// </summary>
    private readonly ITypeFinder _typeFinder = new TypeFinder(_assemblyFinder);

    #endregion private fields

    #region implements IDomainEventHandlerFinder
    /// <inheritdoc/>
    public bool TryGetHandlerType(Type eventDataType, out IEnumerable<RuntimeTypeHandle> eventHandlerTypes)
    {
        eventHandlerTypes = this.GetConfig(eventDataType);

        return eventHandlerTypes.Any();
    }

    #endregion implements IDomainEventHandlerFinder


    /// <summary>
    ///
    /// </summary>
    /// <param name="eventDataType"></param>
    /// <returns></returns>
    private IEnumerable<RuntimeTypeHandle> GetConfig(Type eventDataType)
    {
        if (RuntimeTypeHandles is null)
        {
            while (true)
            {
                if (Interlocked.CompareExchange(ref _lockState, 1, 0) != 0)
                {
                    Thread.Sleep(1);
                    continue;
                }

                try
                {
                    if (RuntimeTypeHandles is null)
                    {
                        var types = _typeFinder.Find(type =>
                        {
                            var typeInfo = type.GetTypeInfo();
                            return typeInfo.GetInterface("IEventHandler") is not null && typeInfo.IsClass &&
                                   !typeInfo.IsAbstract && !typeInfo.IsAssignableFrom(typeof(Object));
                        });

                        RuntimeTypeHandles = types.Select(p => p.TypeHandle).ToArray();
                    }
                }
                finally
                {
                    Interlocked.Exchange(ref _lockState, 0);
                }
            }
        }

        foreach (var runtimeTypeHandle in RuntimeTypeHandles ?? Enumerable.Empty<RuntimeTypeHandle>())
        {
            var type = Type.GetTypeFromHandle(runtimeTypeHandle);
            if (type is null) continue;

            foreach (var _interface in type.GetInterfaces())
            {
                if (_interface.GetGenericArguments().Any(p => p == eventDataType))
                {
                    yield return runtimeTypeHandle;
                    break;
                }
            }
        }
    }
}